package com.hk.core.autoconfigure.exception.reactive;

import com.hk.commons.util.*;
import com.hk.core.authentication.api.reactive.ReactiveSecurityContextUtils;
import com.hk.core.autoconfigure.exception.ExceptionDetails;
import com.hk.core.autoconfigure.exception.UncaughtExceptionConsumer;
import com.hk.core.web.reactive.Webs;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;


/**
 * @author Kevin
 * @date 2020-12-04 17:06
 */
public abstract class AbstractExceptionHandler {

    private static final Lazy<IDGenerator<Long>> idGeneratorLazy = Lazy.of(() ->
            SpringContextHolder.getBeanIfExist(SnowflakeIdGenerator.class).orElse(new SnowflakeIdGenerator()));

    private static final Lazy<UncaughtExceptionConsumer> exceptionConsumerLazy = Lazy.of(() ->
            SpringContextHolder.getBean(UncaughtExceptionConsumer.class));

    static void error(Throwable t, String message, ServerWebExchange exchange) {
        var request = exchange.getRequest();
//        String body = null;
//        try {
//            body = request
//                    .getBody()
//                    .toStream()
//                    .map(item -> item.toString(StandardCharsets.UTF_8))
//                    .findFirst()
//                    .orElse(null);
//        } catch (Exception e) {
//            // ignore
//        }
//        var exceptionDetails = new ExceptionDetails()
//                .setId(idGeneratorLazy.get().generate())
//                .setRequestTime(LocalDateTime.now())
//                .setIp(Webs.getRemoteAddr(request))
//                .setApplicationId(SpringContextHolder.getApplicationId())
//                .setMethod(request.getMethodValue())
//                .setUri(request.getPath().pathWithinApplication().value())//去掉 contextPath后的请求地址
//                .setParameters(Webs.getRequestParam(request))
//                .setBody(body)
//                .setExceptionClassName(t.getClass().getName())
//                .setMessage(StringUtils.defaultIfEmpty(message, t.getMessage()));
//        使用第一种方式 : 直接使用 ReactiveSecurityContextUtils.isAuthenticated() 转换
//        ReactiveSecurityContextUtils.isAuthenticated()
//                .switchIfEmpty(Mono.just(Boolean.FALSE))
//                .flatMap(authenticated -> authenticated ? ReactiveSecurityContextUtils.getPrincipal() : Mono.empty())
//                .doOnNext(principal -> exceptionDetails.setUserId(principal.getUserId())
//                        .setAccount(principal.getAccount()))
////                .doOnSuccess(principal -> System.out.println("doOnSuccess...")) doOnSuccess：在序列完成后会触发，当有错误时，不会触发
//                .doFinally(signalType -> exceptionConsumerLazy.get().accept(exceptionDetails, t)) //相当于 try-catch-finally中的finally
////                .doOnTerminate(() -> exceptionConsumerLazy.get().accept(exceptionDetails, t)) // doOnTerminate 完成或错误终止会触发
//                .subscribe();


//     使用第二种方式：使用 using() 即 try-finally 块方式写法，exceptionDetails先声明，
//     * 有四个参数：
//     * 第一个参数(resourceSupplier)：是一个生成器，用来在subscribe的时候生成要发送的resource对象。
//     * 第二个参数(sourceSupplier)：生成Publisher的工厂，接收resourceSupplier传过来的resource，然后生成Publisher对象。
//     * 第三个参数(resourceCleanup)：用来对resource进行收尾操作。
//     * 第四个参数(eager)：指定收尾工作是否在 subscribe 之前执行
//        Mono.using(() -> exceptionDetails,
//                        details ->
//                                ReactiveSecurityContextUtils.isAuthenticated()
//                                        .switchIfEmpty(Mono.just(Boolean.FALSE))
//                                        .flatMap(authenticated -> authenticated ? ReactiveSecurityContextUtils.getPrincipal() : Mono.empty())
//                                        .doOnNext(principal -> details.setUserId(principal.getUserId()).setAccount(principal.getAccount()))
//                        ,
//                        details -> exceptionConsumerLazy.get().accept(details, t),
//                        true)
//                .subscribe();

//        使用第三种方式，使用 using() 方式写法，即 try-finally 块，exceptionDetails 在resourceSupplier(第一个参数)中生成
        Mono.using(() -> new ExceptionDetails()
                        .setId(idGeneratorLazy.get().generate())
                        .setRequestTime(LocalDateTime.now())
                        .setIp(Webs.getRemoteAddr(request))
                        .setApplicationId(SpringContextHolder.getApplicationId())
                        .setMethod(request.getMethod().name())
                        .setUri(request.getPath().pathWithinApplication().value())//去掉 contextPath后的请求地址
                        .setParameters(Webs.getRequestParam(request))
                        .setExceptionClassName(t.getClass().getName())
                        .setAsync(Boolean.FALSE)
                        .setMessage(StringUtils.defaultIfEmpty(message, t.getMessage())),
                details ->
                        DataBufferUtils.join(request.getBody())
                                .flatMap(dataBuffer -> {
                                    details.setBody(dataBuffer.toString(StandardCharsets.UTF_8));
                                    return ReactiveSecurityContextUtils.isAuthenticated();
                                })
                                .onErrorResume(Exception.class, exception -> ReactiveSecurityContextUtils.isAuthenticated()) //当出现Exception类型错误时恢复
                                .switchIfEmpty(Mono.just(Boolean.FALSE))
                                .flatMap(authenticated -> authenticated ? ReactiveSecurityContextUtils.getPrincipal() : Mono.empty())
                                .doOnNext(principal -> details.setUserId(principal.getUserId()).setAccount(principal.getAccount())),
                details -> exceptionConsumerLazy.get().accept(details, t),
                true).subscribe();
    }
}
